package edu.xpu.compute.recommend.algorithm;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.Resource;

import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood;
import org.apache.mahout.cf.taste.impl.recommender.CachingRecommender;
import org.apache.mahout.cf.taste.impl.recommender.GenericItemBasedRecommender;
import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender;
import org.apache.mahout.cf.taste.impl.similarity.CityBlockSimilarity;
import org.apache.mahout.cf.taste.impl.similarity.EuclideanDistanceSimilarity;
import org.apache.mahout.cf.taste.impl.similarity.LogLikelihoodSimilarity;
import org.apache.mahout.cf.taste.impl.similarity.PearsonCorrelationSimilarity;
import org.apache.mahout.cf.taste.impl.similarity.UncenteredCosineSimilarity;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.recommender.RecommendedItem;
import org.apache.mahout.cf.taste.recommender.Recommender;
import org.apache.mahout.cf.taste.similarity.ItemSimilarity;
import org.apache.mahout.cf.taste.similarity.UserSimilarity;
import org.springframework.stereotype.Component;
/**
 * 协同过滤算法
 * @author liukang
 * @date 2019年10月30日
 */
@Component
public class CollaborativeFilterAlgorithm {
	
	/*
	 * 设置邻接矩阵用户数量
	 */
	private final int NEIGHBORHOOD_NUM = 10;
	@Resource(name="mySqlDataModel")
	private DataModel mysqlDataModel;
	
	/**
	 * 基于用户的推荐算法
	 * @param userID
	 * @param size
	 * @return
	 * @throws TasteException
	 */
	public List<Long> userBasedRecommender(Long userID, int size, Integer similarityType) throws TasteException {
		/*
		 * 1、根据余弦相似度求用户之间的相似度
		 * UncenteredCosineSimilarity:		余弦相似度
		 * 		原理：多维空间两点与所设定的点形成夹角的余弦值
		 * 		范围：[-1,1]，值越大，说明夹角越大，两点相距就越远，相似度就越小
		 * EuclideanDistanceSimilarity:		欧式距离相似度
		 * 		原理：利用欧式距离d定义的相似度s，s=1 / (1+d)
		 * 		范围：[0,1]，值越大，说明d越小，也就是距离越近，则相似度越大
		 * 		同皮尔森相似度一样，该相似度也没有考虑重叠数对结果的影响
		 * PearsonCorrelationSimilarity:	皮尔森相似度
		 * 		原理：用来反映两个变量线性相关程度的统计量
		 * 		范围：[-1,1]，绝对值越大，说明相关性越强，负相关对于推荐的意义小
		 * 		1）不考虑重叠的数量
		 * 		2）如果只有一项重叠，无法计算相似性（计算过程被除数有n-1）
		 * 		3）如果重叠的值都相等，也无法计算相似性（标准差为0，做除数）
		 * SpearmanCorrelationSimilarity:	Spearman秩相关系数相似度
		 * 		原理：Spearman秩相关系数通常被认为是排列后的变量之间的Pearson线性相关系数
		 * 		范围：{-1.0,1.0}，当一致时为1.0，不一致时为-1.0
		 * 		计算非常慢，有大量排序。针对推荐系统中的数据集来讲，用Spearman秩相关系数作为相似度量是不合适的
		 * CityBlockSimilarity:				曼哈顿距离相似度
		 * 		原理：曼哈顿距离的实现，同欧式距离相似，都是用于多维数据空间距离的测度
		 * 		范围：[0,1]，同欧式距离一致，值越小，说明距离值越大，相似度越大
		 * 		比欧式距离计算量少，性能相对高
		 * LogLikelihoodSimilarity:			对数似然相似度
		 * 		原理：重叠的个数，不重叠的个数，都没有的个数
		 * 		范围：具体可去百度文库中查找论文《Accurate Methods for the Statistics of Surprise and Coincidence》
		 * 		处理无打分的偏好数据，比Tanimoto系数的计算方法更为智能
		 * TanimotoCoefficientSimilarity:	Tanimoto系数相似度
		 * 		原理：又名广义Jaccard系数，是对Jaccard系数的扩展，等式为
		 * 		范围：[0,1]，完全重叠时为1，无重叠项时为0，越接近1说明越相似
		 * 		处理无打分的偏好数据,Mahout推荐引擎
		 */
		UserSimilarity similarity  = null;
		/*
		 * 0: 默认。余弦相似度
		 * 1: 欧式距离相似度
		 * 2: 皮尔森相似度
		 * 3: 曼哈顿距离相似度
		 * 4: 对数似然相似度
		 */
		switch (similarityType) {
		case 0:
			similarity  = new UncenteredCosineSimilarity(mysqlDataModel);
			break;
		case 1:
			similarity  = new EuclideanDistanceSimilarity(mysqlDataModel);
			break;
		case 2:
			similarity  = new PearsonCorrelationSimilarity(mysqlDataModel);
			break;
		case 3:
			similarity  = new CityBlockSimilarity(mysqlDataModel);
			break;
		case 4:
			similarity  = new LogLikelihoodSimilarity(mysqlDataModel);
			break;
		}
		//UserSimilarity similarity  = new EuclideanDistanceSimilarity(mysqlDataModel);
		//UserSimilarity similarity  = new UncenteredCosineSimilarity(mysqlDataModel);
		//UserSimilarity similarity  = new PearsonCorrelationSimilarity(mysqlDataModel);
		//UserSimilarity similarity  = new CityBlockSimilarity(mysqlDataModel);
		//UserSimilarity similarity  = new LogLikelihoodSimilarity(mysqlDataModel);
		if (similarity == null) {
			similarity = new EuclideanDistanceSimilarity(mysqlDataModel);
		}
		/*
		 * 2、定义近邻算法
		 * NearestNUserNeighborhood:	指定N的个数，比如，选出前10最相似的用户
		 * ThresholdUserNeighborhood:	指定比例，比如，选择前10%最相似的用户
		 */
		NearestNUserNeighborhood  neighbor = new NearestNUserNeighborhood(NEIGHBORHOOD_NUM, similarity, mysqlDataModel);
		/*
		 * 3、调用基于用户的协同过滤推荐算法
		 */
		Recommender recommender = new CachingRecommender(new GenericUserBasedRecommender(mysqlDataModel, neighbor, similarity));
		/*
		 * 4、获取推荐结果
		 */
		List<RecommendedItem> recommendations = recommender.recommend(userID, size);
		/*
		 * 5、返回推荐给用户的item的id
		 */
		List<Long> recommendItems = new ArrayList<>();
		for(int i = 0; i < recommendations.size(); i++) {
			RecommendedItem recommendedItem = recommendations.get(i);
			recommendItems.add(recommendedItem.getItemID());
		}
		return recommendItems;
    }
	/**
	 * 基于item的推荐算法
	 * @param userID
	 * @param size
	 * @return
	 * @throws TasteException
	 */
	public List<Long> itemBasedRecommender(Long userID, int size, Integer similarityType) throws TasteException {
		/*
		 * 1、根据余弦相似度求用户之间的相似度
		 */
		ItemSimilarity itemSimilarity = null;
		switch (similarityType) {
		case 0:
			itemSimilarity  = new UncenteredCosineSimilarity(mysqlDataModel);
			break;
		case 1:
			itemSimilarity  = new EuclideanDistanceSimilarity(mysqlDataModel);
			break;
		case 2:
			itemSimilarity  = new PearsonCorrelationSimilarity(mysqlDataModel);
			break;
		case 3:
			itemSimilarity  = new CityBlockSimilarity(mysqlDataModel);
			break;
		case 4:
			itemSimilarity  = new LogLikelihoodSimilarity(mysqlDataModel);
			break;
		}
		//UserSimilarity similarity  = new EuclideanDistanceSimilarity(mysqlDataModel);
		//UserSimilarity similarity  = new UncenteredCosineSimilarity(mysqlDataModel);
		//UserSimilarity similarity  = new PearsonCorrelationSimilarity(mysqlDataModel);
		//UserSimilarity similarity  = new CityBlockSimilarity(mysqlDataModel);
		//UserSimilarity similarity  = new LogLikelihoodSimilarity(mysqlDataModel);
		if (itemSimilarity == null) {
			itemSimilarity = new EuclideanDistanceSimilarity(mysqlDataModel);
		}
		
		
		/*
		 * 2、调用基于item的协同过滤推荐算法
		 */
		Recommender recommender = new GenericItemBasedRecommender(mysqlDataModel, itemSimilarity);
		/*
		 * 3、获取推荐结果
		 */
		List<RecommendedItem> recommendations = recommender.recommend(userID, size);
		/*
		 * 4、返回推荐给用户的item的id
		 */
		List<Long> recommendItems = new ArrayList<>();
		for(int i = 0; i < recommendations.size(); i++) {
			RecommendedItem recommendedItem = recommendations.get(i);
			recommendItems.add(recommendedItem.getItemID());
		}
		return recommendItems;
    }

}
